在結束上一篇30天Flutter手滑系列 - 布局組件(Layout Widgets)(下)後,基礎的UI概念應該已經有了,接下來會介紹同樣很重要的導航與路由。
很多時候不管在做Web或Mobile,都需要做跳轉頁面的功能。基本的場景就是當點擊一個連結後,當前頁面會被跳轉到另一個頁面去,例如點擊註冊按鈕後會從首頁/ 跳到註冊頁 /register。
而這些被定義的/、/home、/register名稱都是路由(Router)系統的一部分。
管理這些Route的進出,就是導航(Navigator),在這裡也是一個Navigator Widget。
用來管理進出頁面的機制,本身是一個Stack(堆疊),利用push和pop操作這個堆疊裡的目標。
假設我有一個App,首先我點擊註冊按鈕 -> 點擊返回上一頁 -> 點擊登入按鈕,這時候的Stack其內容的變化會是如下圖所示。
下面實做一個跳轉的畫面:
push到Stack。pop出去 
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Navigation Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}
class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Page')),
      body: Center(
        child: RaisedButton(
          child: Text('Register'),
          onPressed: () {
            Navigator.push(context,
                MaterialPageRoute(builder: (context) => RegisterPage()));
          },
        ),
      ),
    );
  }
}
class RegisterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Register Page'),
      ),
      body: Center(),
    );
  }
}
路由是對頁面的抽象。
簡單來說,每一頁面都有對應的識別名稱,例如/home、/login、/register,讓使用者可以被引導到正確的頁面。
路由又分成兩種:
需要直接註冊路由名稱,不能夠被傳遞參數,但可以接收下一頁返回的值。
我們修改上面的範例,加入routes,並在裡面定義一個/register
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Navigation Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
      routes: <String, WidgetBuilder>{'/register': (_) => new RegisterPage()},
    );
  }
}
然後在onPressed事件稍做修改,改調用Navigator.pushNamed這個方法,內容的字串需要跟routes裡面的名稱完全符合。
class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Page')),
      body: Center(
        child: RaisedButton(
          child: Text('Register'),
          onPressed: () {
            Navigator.of(context).pushNamed('/register');
          },
        ),
      ),
    );
  }
}
這個結果是跟上面只用push的效果是一樣依樣的。
可以傳遞參數,需要自己創建實例。
在這裡示範由HomePage傳遞一段字串到下一頁的效果,然後返回另一段字串內容。
首先,我們修改上一個範例,在pushNamed加入第二個參數arguments,這邊傳遞一個靜態的物件到下一個路由。
class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home Page')),
      body: Center(
        child: RaisedButton(
          child: Text('Register'),
          onPressed: () {
            Navigator.of(context).pushNamed('/register',
                arguments: {'name': 'Raymond'}).then((value) {
              // 新增第二個變數arguments
              showDialog(
                  // 新增一個對話框,用來顯示回傳的值
                  context: context,
                  child: AlertDialog(
                    content: Text(value),
                  ));
            });
          },
        ),
      ),
    );
  }
}
在RegisterPage先新增一個變數,用來儲存接收到的參數,然後新增一個按鈕,讓按下按鈕時,可以回傳一組字串。
class RegisterPage extends StatelessWidget {
  String name; // 宣告一個字串變數
  @override
  Widget build(BuildContext context) {
    dynamic obj = ModalRoute.of(context).settings.arguments;
    name = obj["name"]; // 把接收到的參數存到變數
    return Scaffold(
      appBar: AppBar(
        title: Text('Register Page'),
      ),
      body: Center(
        child: RaisedButton(
          child: Text('Confirm'),
          onPressed: () {
            Navigator.of(context).pop("Hello ${name}"); // 當按下按鈕會返回上一頁,並回傳內容
          },
        ),
      ),
    );
  }
}
在MyHomePage的Navigator加入then的方法,在裡面新增一個showDialog,用來顯示接收到的回傳值。

路由跳轉是很常見的功能,不過概念上就是push跟pop而已。
倒是稍早有查到一篇文章Flutter早知道 - Named Router可以传参了!,裡面提到pushNamed在某個開發版的branch已經可以傳遞參數,但我還沒實驗這部分,如果已經有人有測試過,再麻煩分享一下,或是等我晚點測試看看。
https://api.flutter.dev/flutter/widgets/Navigator-class.html
https://blog.whezh.com/flutter-route-and-navigator/
https://medium.com/flutter-community/flutter-navigation-cheatsheet-a-guide-to-named-routing-dc642702b98c